﻿using System;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Drawing.Imaging;
using Sensors;

namespace GSensorTest
{
    public partial class GSensorTestForm : Form
    {
        public GSensorTestForm()
        {
            InitializeComponent();

            mySensor.OrientationChanged += new OrientationChangedHandler(mySensor_OrientationChanged);
        }

        ScreenOrientation myOrientation = ScreenOrientation.FaceUp;
        void mySensor_OrientationChanged(IGSensor sender)
        {
            myOrientation = sender.Orientation;
        }

        // set up the screen refresh interval
        IGSensor mySensor = GSensorFactory.CreateGSensor();
        private void myTimer_Tick(object sender, EventArgs e)
        {
            Invalidate();
        }

        struct Vector2D
        {
            public Vector2D(double x, double y)
            {
                X = x;
                Y = y;
            }
            public double X;
            public double Y;
            public override string ToString()
            {
                return string.Format("X={0} Y={1}", X, Y);
            }
        }
        
        // describes a ball
        struct Ball
        {
            public int Radius;
            public Vector2D Position;
            public Vector2D Velocity;
        }

        // set up our single ball
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            myBall.Radius = 15;
            myBall.Position = new Vector2D(ClientSize.Width / 2, ClientSize.Height / 2);
            myBall.Velocity = new Vector2D(0, 0);
        }

        Ball myBall = new Ball();

        // keep the ball in the bounds of the screen
        Ball ApplyScreenBounds(Ball ball)
        {
            int maxX = ClientSize.Width - ball.Radius;
            int maxY = ClientSize.Height - ball.Radius;
            if (ball.Position.X < ball.Radius)
            {
                ball.Velocity.X = 0;
                ball.Position.X = ball.Radius;
            }
            else if (ball.Position.X > maxX)
            {
                ball.Velocity.X = 0;
                ball.Position.X = maxX;
            }
            if (ball.Position.Y < ball.Radius)
            {
                ball.Velocity.Y = 0;
                ball.Position.Y = ball.Radius;
            }
            else if (ball.Position.Y > maxY)
            {
                ball.Velocity.Y = 0;
                ball.Position.Y = maxY;
            }
            return ball;
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);
        }

        // Arbitrary constant to make the application "feel" good when playing with the ball.
        // I played around with making the ball work as it should in the "real" world where 
        // the g sensor results would be put into screen space using the DPI of the screen.
        // Although that is more realistic, it is not as fun. :)
        const double myArbitraryFeelGoodConstant = 15;

        // apply physics to a ball
        Ball ApplyDevicePhysics(GVector gVector, Ball ball, int millisecondsElapsed)
        {
            gVector = gVector.Scale(1 / myArbitraryFeelGoodConstant);
            
            ball.Position.X += ball.Velocity.X * millisecondsElapsed / 1000;
            ball.Position.Y += ball.Velocity.Y * millisecondsElapsed / 1000;
            ball.Velocity.X += gVector.X * millisecondsElapsed / 1000;
            ball.Velocity.Y += gVector.Y * millisecondsElapsed / 1000;
            return ball;
        }

        // dots per meter
        double myDPM = 0;

        [DllImport("Coredll")]
        extern static int GetDeviceCaps(IntPtr hdc, int nIndex);

        // I use back buffer to eliminate flickering
        Bitmap myBackBuffer = null;
        Graphics myBackBufferGraphics = null;

        void DisposeBackBuffer()
        {
            if (myBackBuffer != null)
            {
                myBackBuffer.Dispose();
                myBackBuffer = null;
            }
            if (myBackBufferGraphics != null)
            {
                myBackBufferGraphics.Dispose();
                myBackBufferGraphics = null;
            }
        }

        // back buffer is used to keep screen from flickering on paints
        void PrepareBackBuffer()
        {
            if (myBackBuffer == null || myBackBuffer.Width != ClientSize.Width || myBackBuffer.Height != ClientSize.Height)
            {
                DisposeBackBuffer();
                myBackBuffer = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format16bppRgb565);
                myBackBufferGraphics = Graphics.FromImage(myBackBuffer);
            }
        }

        // don't paint the background due to flickering
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            
        }

        // keep track of the last time we painted, so the physics are accurate
        int myLastPaint = Environment.TickCount;
        SolidBrush myCircleBrush = new SolidBrush(Color.Red);
        SolidBrush myTextBrush = new SolidBrush(Color.CornflowerBlue);
        Font myFont = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            // get the DPM if we don't have it
            if (myDPM == 0)
            {
                IntPtr hdc = e.Graphics.GetHdc();
                myDPM = GetDeviceCaps(hdc, 88 /*LOGPIXELSX*/);
                // convert to meter
                myDPM *= 39.3700787; // 1 meter = 39.3700787 inches 
                e.Graphics.ReleaseHdc(hdc);
            }

            // get tick count so we calculate the time elapsed since last paint
            int thisPaint = Environment.TickCount;
            // put the vector into screen space
            GVector gvector = mySensor.GetGVector();
            gvector = gvector.Scale(myDPM); 
            // apply physics to the ball given its previous state and elapsed time
            myBall = ApplyDevicePhysics(gvector, myBall, thisPaint - myLastPaint);
            // restrict the ball to the boundaries of the screen
            myBall = ApplyScreenBounds(myBall);

            // draw to the back buffer
            PrepareBackBuffer();
            myBackBufferGraphics.Clear(SystemColors.Window);
            myBackBufferGraphics.FillEllipse(myCircleBrush, (int)Math.Round(myBall.Position.X - myBall.Radius), (int)Math.Round(myBall.Position.Y - myBall.Radius), myBall.Radius * 2, myBall.Radius * 2);
            myBackBufferGraphics.DrawString(myOrientation.ToString(), myFont, myTextBrush, 0, 0);

            // paint the backbuffer
            e.Graphics.DrawImage(myBackBuffer, 0, 0);

            // set our last paint tick count
            myLastPaint = thisPaint;
        }

        private void myExitMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            if (mySensor != null)
            {
                mySensor.Dispose();
                mySensor = null;
            }
        }
    }
}